﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace MonoStrategy
{
    public class InputOutputBuilding : WorkerBuilding
    {
        /// <summary>
        /// Is called whenever a production cycle for a <see cref="Resource.Max"/> provider
        /// is completed. Please be very careful, since this event is raised (for sake of synchronization)
        /// within the main simulation loop and should be completed within microsecond orders of magnitudes!
        /// </summary>
        internal event DNotifyHandler<InputOutputBuilding> OnProducedCustomItem;

        internal InputOutputBuilding(BuildingManager inParent, BuildingConfig inConfig, Point inPosition)
            :base(inParent, inConfig, inPosition)
        {
        }

        protected class ProviderSelection
        {
            internal GenericResourceStack Provider { get; set; }
            internal Procedure OnProduced { get; set; }
        }

        /// <summary>
        /// This method may be overwritten to weight production probabilities according to
        /// user configuration (weapon and tool smith).
        /// </summary>
        protected virtual ProviderSelection SelectProvider()
        {
            // randomly select a provider and complete production
            int trials = 0;
            GenericResourceStack prov = null;

            do
            {
                prov = Providers[m_Random.Next(0, ProviderConfigs.Count)];
                trials++;

                if (trials > 8)
                    return new ProviderSelection();

            } while (!prov.HasSpace);

            return new ProviderSelection() { Provider = prov };
        }

        /// <summary>
        /// Is not to be called every cycle, since it is an expensive function.
        /// </summary>
        internal override bool Update()
        {
            if (!base.Update())
                return false;

            // already producing?
            long currentTime = Parent.MovMgr.CurrentCycle;
            long timeOffset = (currentTime - ProductionStart);

            if (timeOffset >= ProductionTime)
            {
                // ### START NEW PRODUCTION CYCLE

                // are there missing resources for next production cycle?
                if (UseQualityIndex)
                {
                    if (!Queries.Any(e => { if (e == null) return false; else return e.Available > 0; }))
                        return false;
                }
                else
                {
                    if (!Queries.All(e => { if (e == null) return false; else return e.Available > 0; }))
                        return false;
                }

                // has space for outcomes?
                if (!HasFreeProvider())
                    return false;

                // can select provider?
                if (SelectProvider().Provider == null)
                    return false;

                timeOffset = 0;
                ProductionStart = currentTime;

                // We will directly enter the new production cycle...
            }


            if (timeOffset < ProductionTime)
            {
                // ### UPDATE ONGOING PRODUCTION CYCLE

                // update queries
                for (int i = 0; i < Queries.Count; i++)
                {
                    var cfg = QueriesConfig[i];
                    var query = Queries[i];

                    if (timeOffset != cfg.TimeOffset)
                        continue;

                    /*
                     * When there is set a CycleCount greater one in resource stack config,
                     * one decrease in stack size will last for CycleCount production cycles
                     * without again reducing the stack within that period.
                     */
                    if (QueriesPreload[i] <= 0)
                    {
                        /* 
                         * Is stack still available, since a thief might have stolen it?
                         */
                        if (query.Available == 0)
                        {
                            /*
                             * In case resources are not available during production (stolen)
                             * currently we just ignore it...
                             */
                            continue;
                        }

                        query.RemoveResource();

                        QueriesPreload[i] += Math.Max(0, cfg.CycleCount - 1);
                    }
                    else
                    {
                        // still allowed to use resource from previous cycle (for example fish or meat)
                        QueriesPreload[i]--;
                    }

                    if (UseQualityIndex)
                        break;
                }

                // update providers
                if(Providers.Count > 0)
                {
                    if(timeOffset == Config.ProductionAnimTime)

                    // by assumption all providers are expected to have the same time offset
                    //if (ProviderConfigs[0].TimeOffset == timeOffset)
                    {
                        // has production animation?
                        String animName = null;
                        Int32 dueTimeMillis = 0;
                        
                        if ((animName == null) && VisualUtilities.HasAnimation(this, "Produce"))
                            animName = "Produce";
                        if ((animName == null) && VisualUtilities.HasAnimation(this, "Produce0"))
                            animName = "Produce0";
                        else if ((animName == null) && VisualUtilities.HasAnimation(this, "Succeeded"))
                            animName = "Succeeded";

                        if (animName != null)
                        {
                            VisualUtilities.Animate(this, animName);
                            dueTimeMillis = VisualUtilities.GetDurationMillis(this, animName);

                            if (animName == "Produce0")
                            {
                                VisualUtilities.SynchronizeTask(dueTimeMillis, () => { VisualUtilities.Animate(this, "Produce1"); });

                                dueTimeMillis += VisualUtilities.GetDurationMillis(this, "Produce1");
                            }
                        }

                        Parent.QueueWorkItem(dueTimeMillis, () =>
                        {
                            if(animName != null)
                                VisualUtilities.Animate(this, null);

                            GenericResourceStack prov = Providers[0];

                            if (Providers.Count > 1)
                            {
                                ProviderSelection selection = SelectProvider();

                                prov = selection.Provider;

                                if (selection.OnProduced != null)
                                    selection.OnProduced();
                            }

                            // finally add resource to choosen provider
                            if (prov != null)
                            {
                                if (prov.HasSpace)
                                    prov.AddResource();
                            }
                            else
                            {
                                if (OnProducedCustomItem != null)
                                    OnProducedCustomItem(this);
                            }
                        });
                    }
                }
            }

            return true;
        }
    }
}
